【js】ES Stage/TS 5.2 中的新特性 useing 关键字

using关键字是 tc39 提案ECMAScript Explicit Resource Management提出的,用于为各种资源(内存、I/O 等)提供统一的生命周期管理(何时分配、何时释放等)。

目前(2023.07.02)状态:

  • TS v5.2 率先引入了这个关键字,目前还是在 dev 版本、未成为正式版。
  • ECMAScript Stage: 3(“候选(candidate)”)
  • Last Presented: March, 2023

介绍

using关键字作用是:当离开作用域时,你可以使用 Symbol.dispose 释放掉任何内容。

语法

using

1
2
3
// a synchronously-disposed, block-scoped resource
using x = expr1; // resource w/ local binding
using y = expr2, z = expr4; // multiple resources

结合await关键字:

1
2
3
// an asynchronously-disposed, block-scoped resource
await using x = expr1; // resource w/ local binding
await using y = expr2, z = expr4; // multiple resources

await using可以出现在以下上下文中:

  • 模块的顶层任何允许使用 VariableStatement(变量声明) 的地方,只要它不是立即嵌套在 CaseClause 或 DefaultClause 中即可。
  • 在异步函数或异步生成器的主体中任何允许使用 VariableStatement 的地方,只要它不立即嵌套在 CaseClause 或 DefaultClause 中即可。
  • for-offor-await-of 语句的头部。如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    for (await using x of y) ...
    for await (await using x of y) ...
    // sync iteration, sync disposal
    for (using x of y) ; // no implicit `await` at end of each iteration

    // sync iteration, async disposal
    for (await using x of y) ; // implicit `await` at end of each iteration

    // async iteration, sync disposal
    for await (using x of y) ; // implicit `await` at end of each iteration

    // async iteration, async disposal
    for await (await using x of y) ; // implicit `await` at end of each iteration

ECMA-262 语法说明文档

Symbol.dispose

Symbol.dispose 是 JavaScript 中的一个新的全局符号。任何带有 Symbol.dispose 功能的都被视为“资源”—— “具有特定生命周期的对象” ——并且可以与关键字 using 一起使用。

ecma262-compare: A method that performs explicit resource cleanup on an object. Called by the semantics of the using declaration and DisposableStack objects.

1
2
3
4
5
const resource = {
[Symbol.dispose]: () => {
console.log('Hooray!');
},
};

await using

使用 Symbol.asyncDisposeawait using 来处理需要异步处理的资源。

1
2
3
4
5
6
7
8
const getResource = () => ({
[Symbol.asyncDispose]: async () => {
await someAsyncFunc();
},
});
{
await using resource = getResource();
}

在代码继续执行之前 js 引擎将等待 Symbol.asyncDispose 函数。这对于数据库连接等资源很有用,例如您希望在这些资源中确保连接在程序继续运行之前关闭。

usingawait using的基本区别

主要在Symbol.disposeSymbol.asyncDispose定义上

句法 迭代器 处理器
for (using x of y) @@iterator @@dispose
for (await using x of y) @@iterator @@asyncDispose/@@dispose
for await (using x of y) @@asyncIterator/@@iterator @@dispose
for await (await using x of y) @@asyncIterator/@@iterator @@asyncDispose/@@dispose

如:

1
2
3
4
5
6
7
8
const res = { [Symbol.dispose]() {} };
const asyncRes = { [Symbol.asyncDispose]() {} };

using x = res; // ok: `res` has @@dispose
using x = asyncRes; // throws: `asyncRes` does not have @@dispose

await using x = res; // ok: `res` has @@dispose (fallback)
await using x = asyncres; // ok: `asyncRes` has @@asyncDispose

使用

使用场景很多

文件处理

没有 using

1
2
3
4
5
6
7
8
import { open } from 'node:fs/promises';

let filehandle;
try {
filehandle = await open('thefile.txt', 'r');
} finally {
await filehandle?.close();
}

使用 using

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { open } from "node:fs/promises";
const getFileHandle = async (path: string) => {
const filehandle = await open(path, "r");
return {
filehandle,
[Symbol.asyncDispose]: async () => {
await filehandle.close();
},
};
};
{
await using file = getFileHandle("thefile.txt");
// Do stuff with file.filehandle
} // Automatically disposed!

数据库连接

使用 using 管理数据库连接是 C# 中的一个常见用例,可见文档using 语句 - 确保正确使用可释放对象。Golang 中的defer函数也有点这个意味。

没有 using

1
2
3
4
5
6
const connection = await getDb();
try {
// Do stuff with connection
} finally {
await connection.close();
}

使用 using

1
2
3
4
5
6
7
8
9
10
11
12
13
const getConnection = async () => {
const connection = await getDb();
return {
connection,
[Symbol.asyncDispose]: async () => {
await connection.close();
},
};
};
{
await using { connection } = getConnection();
// Do stuff with connection
} // Automatically closed!

实现机制

从目前`typescript@5.2.0-beta`版(https://www.npmjs.com/package/typescript/v/5.2.0-beta)来看

原代码:

1
2
3
4
5
6
7
8
9
10
{
const getResource = () => {
return {
[Symbol.dispose]: () => {
console.log('using!')
}
}
}
using resource = getResource();
}

tsc 编译后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
var getResource = function () {
var _a;
return (
(_a = {}),
(_a[Symbol.dispose] = function () {
console.log('using!');
}),
_a
);
};
using;
resource = getResource();
}

using 关键字没有处理,看来有可能是无法 polyfill 的语法。最终成为标准后会是如何待后续跟进。


相关链接